package com.geostar.constructionland.attachment.service.impl;

import com.geostar.constructionland.attachment.entity.GeoAttachment;
import com.geostar.constructionland.attachment.entity.RepEntity;
import com.geostar.constructionland.attachment.service.IChunkService;
import com.geostar.constructionland.attachment.util.LocalTmpPathUtil;
import lombok.Synchronized;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.*;
import java.util.ArrayList;
import java.util.List;

/**
 * @author : yell
 * @Description: 本地磁盘文件分片服务
 * @date : 2020/12/30 14:13
 *
 * @remark :
 *  可以由于分片大小的修改 导致相同文件存在两个分片临时文件夹
 *  原分片大小(chunkSize)设置为31457280(30M) 则分片临时文件夹 fileMD5_31457280
 *  调整分片大小为 41943040(40M) 则临时文件夹为 fileMD5_41943040
 */

@Service
@Slf4j
public class LocalChunkServiceImpl implements IChunkService {

    LocalTmpPathUtil localTmpPathUtil;
    @Autowired
    public void setLocalTmpPathUtil(LocalTmpPathUtil localTmpPathUtil){
        this.localTmpPathUtil = localTmpPathUtil;
    }

    /***
     * 分片上传
     * @param geoAttachment 分片上传
     * @return
     */
    @Override
    public boolean uploadChunk(GeoAttachment geoAttachment){
        /**获取当前临时文件文件夹地址*/
        String tmpFilePath = localTmpPathUtil.getChunkFileTempPath(geoAttachment.getMd5(),geoAttachment.getChunkSize(),
                geoAttachment.getChunk(),true);
        try {
            FileOutputStream fileOutputStream = new FileOutputStream(new File(tmpFilePath));
            fileOutputStream.write(geoAttachment.getFileContent());
            fileOutputStream.flush();
            fileOutputStream.close();
            log.info("分片上传成功,地址:{}",tmpFilePath);
        }catch (IOException e){
            e.printStackTrace();
        }
        return true;
    }


    /**
     * 前端多线程上传的情况下 到导致多个线程合并分片，需要加上同步锁
     * @param geoAttachment
     * @return
     * @throws Exception
     */
    @Synchronized
    @Override
    public RepEntity mergeChunks(GeoAttachment geoAttachment) throws Exception{
        /**分片长度**/
        int chunks = geoAttachment.getChunks();
        String md5 = geoAttachment.getMd5();
        boolean uploadDone = isChunkAll(geoAttachment);
        if(uploadDone){
            File wholeFile = new File(localTmpPathUtil.getCompleteFilePath(md5,geoAttachment.getChunkSize(),geoAttachment.getName(),true));
            if(wholeFile.length() > 0 ){
                boolean deleteOk = wholeFile.delete();
                if(deleteOk){
                    wholeFile.createNewFile();
                }
            }
            for (int i = 0; i < chunks ; i++) {
                File partFile = new File(localTmpPathUtil.getChunkFileTempPath(md5,geoAttachment.getChunkSize(),i,false));
                FileOutputStream completeFile = new FileOutputStream(wholeFile, true);
                FileUtils.copyFile(partFile, completeFile);
                completeFile.close();
                FileUtils.deleteQuietly(partFile);
            }
            log.info("合并所有分片生成结果文件:{}" , wholeFile.getCanonicalFile());
            /***对比MD5码*/
            String tempMd5 = DigestUtils.md5Hex(new FileInputStream(wholeFile));
            log.info("临时文件:{},附件:{}",tempMd5,geoAttachment.getMd5());
            if(!tempMd5.equals(geoAttachment.getMd5())){
                // 删除所有文件，md5码不一致
                throw new Exception("MD5码不一致");
            }
            return new RepEntity("200",wholeFile.getPath());
        }
        return new RepEntity("300","未合并结束");
    }


    /**
     * 清空分片信息
     * @param md5 附件MD5码
     * @return
     */
    @Override
    public boolean cleanChunks(String md5) {
        String[] targetPaths = getChunkTmpDirPathByMd5(md5);
        /**删除相关的临时文件**/
        for(String targetPath : targetPaths){
            File targetFile = new File(targetPath);
            targetFile.deleteOnExit();
        }
        return true;
    }

    /***
     * 获取前次上传失败，重新上传的分片信息
     * @param md5 附件md5
     * @param chunkSize 分片大小
     * @return
     */
    @Override
    public List<String> getReUploadChunkInfo(String md5, int chunkSize) {
        List<String> result = new ArrayList<>();
        String[] targetPaths = getChunkTmpDirPathByMd5(md5);
        String chunkTmpDir = localTmpPathUtil.getFileTmpDirPath(md5,chunkSize,false);
        for(String targetPath : targetPaths){
            if(!targetPath.equals(chunkTmpDir)){
                /**删除无用数据**/
                new File(targetPath).deleteOnExit();
            }
        }
        File chunkTmpFile = new File(chunkTmpDir);
        if(chunkTmpFile.exists()){
            String[] fileNames = chunkTmpFile.list();
            for(String fileName : fileNames){
                String chunk = fileName.replaceAll(md5 + "_temp_","");
                result.add(chunk);
            }
        }
        return result;
    }


    @Override
    public boolean isChunkAll(GeoAttachment geoAttachment) {
        /**分片长度**/
        int chunks = geoAttachment.getChunks();
        String md5 = geoAttachment.getMd5();
        /***校验文件是否完整*/
        boolean uploadDone = true;
        for (int i = 0; i < chunks; i++) {
            File partFile = new File(localTmpPathUtil.getChunkFileTempPath(md5,geoAttachment.getChunkSize(),i,false));
            if (!partFile.exists()) {
                log.info("{}文件不存在,等待继续上传;",localTmpPathUtil.getChunkFileTempPath(md5,geoAttachment.getChunkSize(),i,false));
                uploadDone = false;
                break;
            }
        }
        return uploadDone;
    }

    /**
     * 根据MD5码获取分片临时文件夹
     * @param md5
     * @return
     */
    private String[] getChunkTmpDirPathByMd5(String md5){
        /**获取临时目录**/
        String tmpPath = localTmpPathUtil.getTmpPath();
        File tmpFileDir = new File(tmpPath);
        /**查询此文件存在的分片临时目录**/
        String[] targetPaths = tmpFileDir.list(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                if(name.startsWith(md5)){
                    return true;
                }
                return false;
            }
        });
        return targetPaths;
    }
}
